package client

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"strings"
)

type PostData struct {
	GroupName string `json:"GroupName" form:"GroupName"`
	Key       string `json:"key" form:"key"`
	Value     []byte `json:"value" form:"value"`
}

var CommandList = [][]string{
	{"SETGROUP", "groupName", "STRING"},
	{"SET", "key value", "STRING"},
	{"GET", "key", "STRING"},
	{"DELETE", "key", "STRING"},
}

func PrintCmdHelp() {
	help := `
 Thanks for using dCache
 dcache-cli
 To get help about command:
	Type: "help <command>" for help on command
 To quit:
	<ctrl+c> or <quit>`
	fmt.Println(help)
}

func ParseCommandLine(cmdLine string) (string, []interface{}) {
	commands := strings.Split(cmdLine, " ")
	if len(commands) == 0 {
		return "", nil
	}

	args := make([]interface{}, 0)
	for i := 1; i < len(commands); i++ {
		args = append(args, commands[i])
	}

	return commands[0], args
}

func HandleCmd(addr string, cmd string, args []interface{}) (node string, result string, err string) {
	switch cmd {
	case "setgroup":
		node, result, err = handleSetGroup(addr, cmd, args)
	case "delgroup":
		node, result, err = handleDeleteGroup(addr, cmd, args)
	case "set":
		node, result, err = handleSet(addr, cmd, args)
	case "get":
		node, result, err = handleGet(addr, cmd, args)
	case "delete":
		node, result, err = handleDelete(addr, cmd, args)
	default:
		node, result, err = "", "", "Invalid command"
	}

	return
}

// Set group
func handleSetGroup(addr string, cmd string, args []interface{}) (nodes string, result string, errString string) {
	// check input arguments
	if (len(args)) != 1 {
		return "", "", "Only 1 group name is required, please check your input"
	}

	// call api POST
	res, err := http.Post("http://"+addr+"/1.0/api/Groups/"+args[0].(string), "", nil)
	if err != nil {
		return "", "", err.Error()
	}

	temp := make(map[string]interface{})
	// read http response body
	data, err1 := ioutil.ReadAll(res.Body)
	if err1 != nil {
		return "", "", err1.Error()
	}

	// unmashal body data into temp
	if err1 := json.Unmarshal(data, &temp); err1 != nil {
		return "", "", err1.Error()
	}

	// status code is OK
	if res.StatusCode == http.StatusOK {
		return temp["node"].(string), "OK", ""
	}

	// status code is not OK
	return temp["node"].(string), "nil", temp["err"].(string)
}

// Delete group
func handleDeleteGroup(addr string, cmd string, args []interface{}) (node string, result string, errString string) {
	if (len(args)) != 1 {
		return "", "", "Only 1 group name is required, please check your input"
	}

	// call api custom DELETE
	targetUrl := "http://" + addr + "/1.0/api/Groups/" + args[0].(string)
	req, _ := http.NewRequest("DELETE", targetUrl, nil)
	res, err := http.DefaultClient.Do(req)

	if err != nil {
		return "", "", err.Error()
	}

	temp := make(map[string]interface{})
	// read http response body
	data, err1 := ioutil.ReadAll(res.Body)
	if err1 != nil {
		return "", "", err1.Error()
	}

	// unmashal body data into temp
	if err1 := json.Unmarshal(data, &temp); err1 != nil {
		return "", "", err1.Error()
	}

	// status code is OK
	if res.StatusCode == http.StatusOK {
		return "", "OK", ""
	}

	return "", "", temp["err"].(string)
}

// Create a key-value
func handleSet(addr string, cmd string, args []interface{}) (node string, result string, errString string) {
	if (len(args)) != 3 {
		return "", "", "Set needs 3 arguments in sequence: groupname, key, value. Please check your input"
	}

	groupName := args[0].(string)
	key := args[1].(string)
	value := []byte(args[2].(string))

	p := &PostData{
		GroupName: groupName,
		Key:       key,
		Value:     value,
	}

	requestBody := new(bytes.Buffer)
	json.NewEncoder(requestBody).Encode(p)

	//call api POST
	res, err := http.Post("http://"+addr+"/1.0/api/keys", "application/json", requestBody)
	if err != nil {
		return "", "", err.Error()
	}

	temp := make(map[string]interface{})
	// read http response body
	data, err1 := io.ReadAll(res.Body)
	if err1 != nil {
		return "", "", err1.Error()
	}

	// unmashal body data into temp
	if err1 := json.Unmarshal(data, &temp); err1 != nil {
		return "", "", err1.Error()
	}

	// status code is OK
	if res.StatusCode == http.StatusOK {
		return temp["node"].(string), "OK", ""
	}

	// status code is not OK
	return temp["node"].(string), "nil", temp["err"].(string)
}

// Get a value by group and key
func handleGet(addr string, cmd string, args []interface{}) (node string, result string, errString string) {
	if (len(args)) != 2 {
		return "", "", "Set needs 2 arguments in sequence: groupname, key. Please check your input"
	}

	groupName := args[0].(string)
	key := args[1].(string)

	p := &PostData{
		GroupName: groupName,
		Key:       key,
	}

	requestBody := new(bytes.Buffer)
	json.NewEncoder(requestBody).Encode(p)

	// call api, custom GET
	res, err := http.Get("http://" + addr + "/1.0/api/keys" + fmt.Sprintf("?GroupName=%s&key=%s", groupName, key))

	if err != nil {
		return "", "", err.Error()
	}

	temp := make(map[string]interface{})
	// read http response body
	data, err1 := io.ReadAll(res.Body)
	if err1 != nil {
		return "", "", err1.Error()
	}

	// unmashal body data into temp
	if err1 := json.Unmarshal(data, &temp); err1 != nil {
		return "", "", err1.Error()
	}

	// status code is OK
	if res.StatusCode == http.StatusOK {
		return temp["node"].(string), temp["value"].(string), ""
	}

	// status code is not OK
	return temp["node"].(string), "nil", temp["err"].(string)
}

// Delete a key-value by group and key
func handleDelete(addr string, cmd string, args []interface{}) (node string, result string, errString string) {
	if (len(args)) != 2 {
		return "", "", "arguments invalid"
	}

	groupName := args[0].(string)
	key := args[1].(string)

	p := &PostData{
		GroupName: groupName,
		Key:       key,
	}

	requestBody := new(bytes.Buffer)
	json.NewEncoder(requestBody).Encode(p)

	// call api custom DELETE
	targetUrl := "http://" + addr + "/1.0/api/keys"
	req, _ := http.NewRequest("DELETE", targetUrl, requestBody)
	req.Header.Set("Content-Type", "application/json")
	res, err := http.DefaultClient.Do(req)

	if err != nil {
		return "", "", err.Error()
	}

	temp := make(map[string]interface{})
	// read http response body
	data, err1 := ioutil.ReadAll(res.Body)
	if err1 != nil {
		return "", "", err1.Error()
	}

	// unmashal body data into temp
	if err1 := json.Unmarshal(data, &temp); err1 != nil {
		return "", "", err1.Error()
	}

	// status code is OK
	if res.StatusCode == http.StatusOK {
		return temp["node"].(string), "OK", ""
	}

	// status code is not OK
	return temp["node"].(string), "nil", temp["err"].(string)
}
